/* path.c */

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <ctype.h>

#include "path.h"
#include "parse.h"

static int point_dist(PATH_POINT *p1, PATH_POINT *p2)
{
  float dx = p1->x - p2->x;
  float dy = p1->y - p2->y;
  int dist = (int) floor(sqrt(dx*dx + dy*dy));
  return (dist <= 0) ? 1 : dist;
}

/* Reset a path */
void path_clear(PATH *path)
{
  path->num_points = 0;
  path->num_spawn_points = 0;
  path->bg_num = 0;
  path->statue.x = 256;
  path->statue.y = 256;
  path->statue.radius = 32;
}

int path_is_spawn_point(PATH *path, int point)
{
  int i;

  for (i = 0; i < path->num_spawn_points; i++)
    if (path->spawn_points[i] == point)
      return 1;
  return 0;
}

void path_remove_spawn_point(PATH *path, int point)
{
  int i;

  for (i = 0; i < path->num_spawn_points; i++)
    if (path->spawn_points[i] == point) {
      for (; i < path->num_spawn_points-1; i++)
        path->spawn_points[i] = path->spawn_points[i+1];
      path->num_spawn_points--;
    }
}

void path_set_spawn_point(PATH *path, int point)
{
  if (path->num_spawn_points >= PATH_NUM_SPAWN_POINTS
      || path_is_spawn_point(path, point))
    return;
  path->spawn_points[path->num_spawn_points++] = point;
}

/* Return the segment at the position (x, y) */
int path_get_segment_at(PATH *path, int x, int y, int *ppoint, int *psegment)
{
  int i, j;

  for (i = 0; i < path->num_points; i++) {
    PATH_POINT *p1 = path->points + i;
    for (j = 0; j < p1->num_segs; j++) {
      PATH_POINT *p2 = path->points + p1->segs[j].dest_point;
      double sx, sy, ds, rx, ry, d;

      sx = p2->x - p1->x;
      sy = p2->y - p1->y;
      ds = sqrt(sx*sx + sy*sy);
      rx = x - p1->x;
      ry = y - p1->y;

      /* distance from point to segment */
      d = fabs(sx*ry - sy*rx) / ds;
      if (d > 5.)
        continue;

      /* project dot on segment and check if it's inside */
      d = (sx*rx + sy*ry) / (ds * ds);
      if (d < 0 || d > 1.)
        continue;
      
      *ppoint = i;
      *psegment = j;
      return 1;
    }
  }
  *ppoint = *psegment = -1;
  return 0;
}

/* Return the point at a position */
int path_get_point_at(PATH *path, int x, int y)
{
  int i;

  for (i = 0; i < path->num_points; i++) {
    int dx = x - path->points[i].x;
    int dy = y - path->points[i].y;
    
    if (dx*dx + dy*dy < 5*5)
      return i;
  }
  return -1;
}

/* Create a segment connecting two points, return the new segment id */
int path_create_segment(PATH *path, int src_point, int dest_point, int weight)
{
  PATH_SEGMENT *seg;
  PATH_POINT *src, *dest;
  int i;

  if (src_point >= path->num_points || dest_point >= path->num_points)
    return -1;

  src = path->points + src_point;
  dest = path->points + dest_point;

  /* check if segment already exists */
  for (i = 0; i < src->num_segs; i++)
    if (src->segs[i].dest_point == dest_point)
      return -1;

  if (src->num_segs >= PATH_NUM_SEGS_PER_POINT)
    return -1;

  seg = src->segs + src->num_segs;
  seg->steps = point_dist(src, dest);
  seg->dest_point = dest_point;
  seg->weight = 1;
  seg->start_level = 0;
  
  return src->num_segs++;
}

/* Create a new point, return the new point id */
int path_create_point(PATH *path, int x, int y)
{
  PATH_POINT *point;

  if (path->num_points >= PATH_NUM_POINTS)
    return -1;

  point = path->points + path->num_points;
  point->x = x;
  point->y = y;
  point->num_segs = 0;

  return path->num_points++;
}

/* Move a point to the given location and recalculate all involved segment sizes */
void path_move_point(PATH *path, int point_num, int x, int y)
{
  PATH_POINT *point, *p;
  int i, j;

  if (point_num < 0 || point_num >= path->num_points)
    return;

  point = path->points + point_num;
  point->x = x;
  point->y = y;

  /* fix segments originating at point */
  for (j = 0; j < point->num_segs; j++)
    point->segs[j].steps = point_dist(point, &path->points[point->segs[j].dest_point]);

  /* fix segments arriving at point */
  for (i = 0; i < path->num_points; i++) {
    if (i == point_num)
      continue;
    p = path->points + i;
    for (j = 0; j < p->num_segs; j++) {
      if (p->segs[j].dest_point == point_num)
	p->segs[j].steps = point_dist(point, p);
    }
  }
}

/* Remove a segment */
void path_remove_segment(PATH *path, int point_num, int seg_num)
{
  int i;
  PATH_POINT *point;

  if (point_num < 0 || point_num >= path->num_points)
    return;
  
  point = path->points + point_num;
  if (seg_num < 0 || seg_num >= point->num_segs)
    return;

  for (i = seg_num; i < point->num_segs-1; i++)
    point->segs[i] = point->segs[i+1];

  point->num_segs--;
}

/* Remove a point */
void path_remove_point(PATH *path, int point_num)
{
  int i, j;
  PATH_POINT *p;

  if (point_num < 0 || point_num >= path->num_points)
    return;

  /* remove all segments to this point */
  for (i = 0; i < path->num_points; i++) {
    if (i == point_num)
      continue;
    p = path->points + i;
    for (j = 0; j < p->num_segs; j++) {
      if (p->segs[j].dest_point == point_num)
	path_remove_segment(path, i, j);
    }
  }

  /* shift points down */
  for (i = point_num; i < path->num_points-1; i++)
    path->points[i] = path->points[i+1];

  /* fix references to points above the one deleted */
  for (i = 0; i < path->num_points-1; i++) {
    p = path->points + i;
    for (j = 0; j < p->num_segs; j++) {
      if (p->segs[j].dest_point > point_num)
	p->segs[j].dest_point--;
    }
  }

  path->num_points--;
}

/* Get the coordinates of the endpoints of a segment */
int path_get_segment_endpoints(PATH *path, int point_num, int segment_num, int *x1, int *y1, int *x2, int *y2)
{
  PATH_POINT *p1, *p2;

  if (point_num < 0 || point_num >= path->num_points)
    return 1;
  
  p1 = path->points + point_num;
  if (segment_num < 0 || segment_num >= p1->num_segs)
    return 1;

  p2 = path->points + p1->segs[segment_num].dest_point;
  *x1 = p1->x;
  *y1 = p1->y;
  *x2 = p2->x;
  *y2 = p2->y;
  return 0;
}

static void export_convert_point_pos(int x, int y, int *px, int *py)
{
  *px = x - 256;
  *py = 256 - y;
}

/* Export the path to a C source file */
int path_export(PATH *path, const char *filename)
{
  FILE *f;
  int i, j;
  char name[256];
  const char *p;

  /* ganerate name based on filename */
  p = strrchr(filename, '/');
  if (p)
    p++;
  else
    p = filename;
  for (i = j = 0; j+1 < sizeof(name) && p[i] != '\0'; i++) {
    if (p[i] == '.')
      break;
    if (isalnum(p[i]) || p[i] == '_')
      name[j++] = p[i];
    else
      name[j++] = '_';
  }
  name[j] = '\0';

  /* open file */
  f = fopen(filename, "wb");
  if (f == NULL)
    return 1;

  fprintf(f, "/* Generated by PonyEdit, do not edit! */\n");
  fprintf(f, "\nstatic PATH path_%s = {\n", name);

  /* background */
  fprintf(f, "  %d,\n\n", path->bg_num);

  /* statue */
  {
    int x, y;
    export_convert_point_pos(path->statue.x, path->statue.y, &x, &y);
    fprintf(f, "  { %d, %d, %d },\n\n", x, y, path->statue.radius);
  }

  /* spawn points */
  fprintf(f, "  %d, {", path->num_spawn_points);
  for (i = 0; i < path->num_spawn_points; i++)
    fprintf(f, " %d,", path->spawn_points[i]);
  fprintf(f, " },\n\n");

  /* points and segments */
  fprintf(f, "  %d,\n  {\n", path->num_points);
  for (i = 0; i < path->num_points; i++) {
    PATH_POINT *p = path->points + i;
    int px, py;

    export_convert_point_pos(p->x, p->y, &px, &py);
    if (p->num_segs == 0)
      fprintf(f, "    { %d, %d, %d, {} },\n", px, py, p->num_segs);
    else {
      fprintf(f, "    {\n");
      fprintf(f, "      %d, %d, %d,\n", px, py, p->num_segs);
      fprintf(f, "      {\n");
      for (j = 0; j < p->num_segs; j++) {
	PATH_SEGMENT *seg = p->segs + j;
	fprintf(f, "        { %d, %d, %d, %d },\n", seg->steps, seg->dest_point, seg->weight, seg->start_level);
      }
      fprintf(f, "      }\n");
      fprintf(f, "    },\n");
    }
  }
  fprintf(f, "  }\n");
  fprintf(f, "};\n");

  fclose(f);
  return 0;
}

static void import_convert_point_pos(int x, int y, int *px, int *py)
{
  *px = x + 256;
  *py = 256 - y;
}

static int import_number(PARSE_STATE *state, int *pnum)
{
  long num;
  char *end, *token;

  token = parse_get_token(state);
  if (token == NULL) {
    printf("ERROR: expecting number, got EOF in file '%s'\n", state->filename);
    return 0;
  }
  
  num = strtol(token, &end, 10);
  if (end == NULL || *end != '\0') {
    printf("ERROR: expecting number, got '%s' in file '%s'\n", token, state->filename);
    return 0;
  }

  *pnum = (int) num;
  return 1;
}

static int expected_token(PARSE_STATE *state, const char *token)
{
  char *read_token = parse_get_token(state);

  if (read_token == NULL) {
    printf("ERROR: expected '%s', found EOF in file '%s'\n", token, state->filename);
    return 0;
  }
  if (strcmp(read_token, token) != 0) {
    printf("ERROR: expected '%s', found '%s' in file '%s'\n", token, read_token, state->filename);
    return 0;
  }
  return 1;
}

static int optional_token(PARSE_STATE *state, const char *token)
{
  char *read_token = parse_get_token(state);

  if (read_token == NULL)
    return 0;

  if (strcmp(read_token, token) != 0) {
    parse_unget_token(state);
    return 0;
  }

  return 1;
}

static int import_segment(PARSE_STATE *state, PATH_SEGMENT *seg)
{
  /* open segment def */
  if (! expected_token(state, "{"))
    return 0;

  if (! import_number(state, &seg->steps))
    return 0;
  if (! expected_token(state, ","))
    return 0;
  if (! import_number(state, &seg->dest_point))
    return 0;
  if (! expected_token(state, ","))
    return 0;
  if (! import_number(state, &seg->weight))
    return 0;
  if (! expected_token(state, ","))
    return 0;
  if (! import_number(state, &seg->start_level))
    return 0;
  optional_token(state, ",");

  /* close segment def */
  if (! expected_token(state, "}"))
    return 0;

  return 1;
}

static int import_point(PARSE_STATE *state, PATH_POINT *point)
{
  int x, y;

  /* open point def */
  if (! expected_token(state, "{"))
    return 0;

  /* position */
  if (! import_number(state, &x))
    return 0;
  if (! expected_token(state, ","))
    return 0;
  if (! import_number(state, &y))
    return 0;
  if (! expected_token(state, ","))
    return 0;
  import_convert_point_pos(x, y, &point->x, &point->y);

  /* segments */
  if (! import_number(state, &point->num_segs))
    return 0;
  if (! expected_token(state, ","))
    return 0;
  if (! expected_token(state, "{"))
    return 0;
  for (x = 0; x < point->num_segs; x++) {
    if (! import_segment(state, point->segs + x))
      return 0;
    optional_token(state, ",");
  }
  if (! expected_token(state, "}"))
    return 0;

  /* close point def */
  if (! expected_token(state, "}"))
    return 0;

  return 1;
}

static int import_statue(PARSE_STATE *state, PATH_STATUE *statue)
{
  int x, y;

  /* open statue def */
  if (! expected_token(state, "{"))
    return 0;

  if (! import_number(state, &x))
    return 0;
  if (! expected_token(state, ","))
    return 0;

  if (! import_number(state, &y))
    return 0;
  if (! expected_token(state, ","))
    return 0;

  if (! import_number(state, &statue->radius))
    return 0;
  optional_token(state, ",");

  /* close statue def */
  if (! expected_token(state, "}"))
    return 0;

  import_convert_point_pos(x, y, &statue->x, &statue->y);
  return 1;
}

static int read_path(PATH *path, PARSE_STATE *state)
{
  int i;

  /* open path def */
  if (! expected_token(state, "{"))
    return 0;

  /* bg num */
  if (! import_number(state, &path->bg_num))
    return 0;
  if (! expected_token(state, ","))
    return 0;

  /* statue */
  if (! import_statue(state, &path->statue))
    return 0;
  if (! expected_token(state, ","))
    return 0;

  /* spawn points */
  if (! import_number(state, &path->num_spawn_points))
    return 0;
  if (! expected_token(state, ","))
    return 0;
  if (! expected_token(state, "{"))
    return 0;
  for (i = 0; i < path->num_spawn_points; i++) {
    if (! import_number(state, path->spawn_points + i))
      return 0;
    optional_token(state, ",");
  }
  if (! expected_token(state, "}"))
    return 0;
  if (! expected_token(state, ","))
    return 0;

  /* points */
  if (! import_number(state, &path->num_points))
    return 0;
  if (! expected_token(state, ","))
    return 0;
  if (! expected_token(state, "{"))
    return 0;
  for (i = 0; i < path->num_points; i++) {
    if (! import_point(state, path->points + i))
      return 0;
    optional_token(state, ",");
  }
  if (! expected_token(state, "}"))
    return 0;

  /* close path def */
  if (! expected_token(state, "}"))
    return 0;

  return 1;
}

/* Import the path from a C source file */
int path_import(PATH *path, const char *filename, char *name, int max_name_len)
{
  static char *expected_start[] = { "static", "PATH", NULL, "=" };
  static char *expected_end[]  = { ";" };
  PARSE_STATE state;
  char *token;
  int i;

  if (parse_open(&state, filename) != 0) {
    printf("ERROR: can't open '%s'\n", filename);
    return 1;
  }

  /* read starting tokens and extract path name */
  for (i = 0; i < sizeof(expected_start) / sizeof(expected_start[0]); i++) {
    if (expected_start[i] == NULL) {
      token = parse_get_token(&state);
      if (token == NULL) {
	parse_close(&state);
	printf("ERROR: can't read path name from '%s'\n", filename);
	return 1;
      }
      strncpy(name, token, max_name_len);
    } else if (! expected_token(&state, expected_start[i])) {
      parse_close(&state);
      return 1;
    }
  }

  /* read path data */
  if (! read_path(path, &state)) {
    parse_close(&state);
    return 1;
  }

  /* read expected end */
  for (i = 0; i < sizeof(expected_end)/sizeof(expected_end[0]); i++) {
    if (! expected_token(&state, expected_end[i])) {
      parse_close(&state);
      return 1;
    }
  }

  parse_close(&state);
  return 0;
}
